﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CriterionFactory.cs" company="XamlNinja">
//   2011 Richard Griffin and Ollie Riches
// </copyright>
// <summary>
//   CriterionFactory.cs
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace WP7Contrib.Services.BingMaps
{
    using System;
    using System.Collections.Generic;
    using System.Device.Location;
    using System.Globalization;
    using System.Windows;
    using Collections;
    using Microsoft.Phone.Controls.Maps;
    using Model;
    using MapLayer = Model.MapLayer;

    /// <summary>
    /// Helper class for creation Bing Maps API search criteria, Bing Maps API exposes a large number of permutations and this class is there to help reduce the 
    /// complexity and confusion. The class also performs simple validation for criteria, e.g. if searching by address the address can not be null or empty.
    /// </summary>
    public static class CriterionFactory
    {
        #region LocationSearchForPoint

        public static ILocationSearchPointCriterion CreateLocationSearchForPoint(GeoCoordinate point)
        {
            return CreateLocationSearchForPoint(point, new List<LocationEntity>());
        }

        public static ILocationSearchPointCriterion CreateLocationSearchForPoint(GeoCoordinate point, IList<LocationEntity> includeEntities)
        {
            if (point == null)
                throw new ArgumentNullException("point", "Point is null.");

            if (point == GeoCoordinate.Unknown)
                throw new ArgumentException("Point is unknown.", "point");

            if (includeEntities == null)
                throw new ArgumentNullException("includeEntities", "Include Entities can not be null.");

            var criterion = new LocationSearchCriterion { Point = point };
            criterion.IncludedEntities.AddRange(includeEntities);

            return criterion;
        }

        #endregion

        #region LocationSearchForAddress

        public static ILocationSearchAddressCriterion CreateLocationSearchForAddress(string postCode)
        {
            var address = new Address { PostalCode = postCode };
            return CreateLocationSearchForAddress(address);
        }

        public static ILocationSearchAddressCriterion CreateLocationSearchForAddress(string countryRegion, string postCode)
        {
            var address = new Address {CountryRegion = countryRegion, PostalCode = postCode};
            return CreateLocationSearchForAddress(address);
        }

        public static ILocationSearchAddressCriterion CreateLocationSearchForAddress(string addressLine, string countryRegion, string postCode)
        {
            var address = new Address { AddressLine = addressLine, CountryRegion = countryRegion, PostalCode = postCode };
            return CreateLocationSearchForAddress(address);
        }

        public static ILocationSearchAddressCriterion CreateLocationSearchForAddress(Address address)
        {
            if (address == null)
                throw new ArgumentNullException("address", "Address is null.");

            if (address == Address.Empty)
                throw new ArgumentException("Address is empty.", "address");

            var criterion = new LocationSearchCriterion { Address = address };

            return criterion;
        }

        #endregion

        #region CreateRouteSearch

        public static IRouteSearchCriterion CreateRouteSearch(IList<WayPoint> wayPoints)
        {
            return CreateRouteSearch(wayPoints, ModeOfTravel.Driving, DistanceUnit.Kilometer, null, null, null, RoutePathOutput.Points, null, TimeType.Departure, null, null);
        }

        public static IRouteSearchCriterion CreateRouteSearch(IList<WayPoint> wayPoints, ModeOfTravel modeOfTravel)
        {
            return CreateRouteSearch(wayPoints, modeOfTravel, DistanceUnit.Kilometer, null, null, null, RoutePathOutput.Points, null, TimeType.Departure, null, null);
        }

        public static IRouteSearchCriterion CreateRouteSearch(IList<WayPoint> wayPoints, ModeOfTravel modeOfTravel, DistanceUnit distanceUnit, Optimize? optimize)
        {
            return CreateRouteSearch(wayPoints, modeOfTravel, distanceUnit, optimize, null, null, RoutePathOutput.Points, null, TimeType.Departure, null, null);
        }

        public static IRouteSearchCriterion CreateRouteSearch(IList<WayPoint> wayPoints, ModeOfTravel modeOfTravel, DistanceUnit distanceUnit, Optimize? optimize, Avoid avoid, int? heading, RoutePathOutput routePathOutput, DateTime? dateTime, TimeType timeType, int? maxSolutions, string pointOfInterest)
        {
            if (wayPoints == null || wayPoints.Count < 2)
                throw new ArgumentException("Minimum number of WayPoints is 2.", "wayPoints");

            var criterion = new RouteSearchCriterion
            {
                TravelMode = modeOfTravel,
                DistanceUnit = distanceUnit,
                Optimize = optimize,
                Avoid = avoid ?? new Avoid(),
                Heading = heading,
                PathOutput = routePathOutput,
                DateTime = dateTime,
                TimeType = timeType,
                MaxSolutions = maxSolutions.HasValue ? maxSolutions.GetValueOrDefault() : 1,
                PointOfInterest = pointOfInterest
            };
            criterion.WayPoints.AddRange(wayPoints);
            return criterion;
        }

        #endregion

        #region CreateRouteSearchFromMajorRoads

        public static IRouteSearchMajorRoadCriterion CreateRouteSearchFromMajorRoads(RouteDestination routeDestination, DistanceUnit distanceUnit)
        {
            return CreateRouteSearchFromMajorRoads(routeDestination, distanceUnit, RoutePathOutput.None, RouteExclude.Undefined);
        }

        public static IRouteSearchMajorRoadCriterion CreateRouteSearchFromMajorRoads(RouteDestination routeDestination, DistanceUnit distanceUnit, RoutePathOutput pathOutput)
        {
            return CreateRouteSearchFromMajorRoads(routeDestination, distanceUnit, pathOutput, RouteExclude.Undefined);
        }

        public static IRouteSearchMajorRoadCriterion CreateRouteSearchFromMajorRoads(RouteDestination routeDestination, DistanceUnit distanceUnit, RoutePathOutput pathOutput, RouteExclude exclude)
        {
            return new RouteSearchCriterion
            {
                Destination = routeDestination,
                DistanceUnit = distanceUnit,
                PathOutput = pathOutput,
                Exclude = exclude
            };
        }

        #endregion

        #region CreateImageryUrlForRoute

        public static ImagerySearchCriterion CreateImageryUrlForRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints)
        {
            return CreateImageryUrlForRoute(imagerySet, modeOfTravel, wayPoints, null, MapLayer.TrafficFlow, Size.Empty, 1, MapVersion.V1, null, new List<PushPin>(), TimeType.Departure);
        }

        public static ImagerySearchCriterion CreateImageryUrlForRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints, Optimize? optimize)
        {
            return CreateImageryUrlForRoute(imagerySet, modeOfTravel, wayPoints, null, null, null, null, MapVersion.V1, optimize, new List<PushPin>(), TimeType.Departure);
        }

        public static ImagerySearchCriterion CreateImageryUrlForRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints, IList<PushPin> pushPins)
        {
            return CreateImageryUrlForRoute(imagerySet, modeOfTravel, wayPoints, null, MapLayer.TrafficFlow, Size.Empty, 1, MapVersion.V1, null, pushPins, TimeType.Departure);
        }

        public static ImagerySearchCriterion CreateImageryUrlForRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints, Optimize? optimize, IList<PushPin> pushPins)
        {
            return CreateImageryUrlForRoute(imagerySet, modeOfTravel, wayPoints, null, MapLayer.TrafficFlow, Size.Empty, 1, MapVersion.V1, optimize, pushPins, TimeType.Departure);
        }

        public static ImagerySearchCriterion CreateImageryUrlForRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints, DateTime dateTime, Optimize? optimize, IList<PushPin> pushPins, TimeType timeType)
        {
            return CreateImageryUrlForRoute(imagerySet, modeOfTravel, wayPoints, dateTime, MapLayer.TrafficFlow, Size.Empty, 1, MapVersion.V1, optimize, pushPins, timeType);
        }

        public static ImagerySearchCriterion CreateImageryUrlForRoute(ImagerySet imagerySet,
                                                                      ModeOfTravel modeOfTravel,
                                                                      IList<WayPoint> wayPoints,
                                                                      DateTime? dateTime,
                                                                      MapLayer? layer,
                                                                      Size? size,
                                                                      int? maxSolutions,
                                                                      MapVersion? mapVersion,
                                                                      Optimize? optimize,
                                                                      IList<PushPin> pushPins,
                                                                      TimeType? timeType)
        {
            if (wayPoints == null || wayPoints.Count < 2)
                throw new ArgumentException("Minimum number of WayPoints is 2.","wayPoints");

            if (pushPins != null && pushPins.Count > 18)
                throw new ArgumentOutOfRangeException("pushPins", "Maximum number of PushPins is 18.");

            var criterion = new ImagerySearchCriterion
            {
                ImagerySet = imagerySet,
                WayPoints = new ObservableCollection<WayPoint>(wayPoints),
                DateTime = dateTime,
                MapLayer = layer,
                MapSize = size,
                MaxSolutions = maxSolutions,
                MapVersion = mapVersion,
                Optimize = optimize,
                PushPins = new ObservableCollection<PushPin>(pushPins),
                TimeType = timeType,
                TravelMode = modeOfTravel
            };

            return criterion;
        }

        #endregion

        #region CreateImageryUrlForQuery

        public static ImagerySearchCriterion CreateImageryUrlForQuery(ImagerySet imagerySet, string query)
        {
            return CreateImageryUrlForQuery(imagerySet, query, MapLayer.TrafficFlow, Size.Empty, MapVersion.V1);
        }

        public static ImagerySearchCriterion CreateImageryUrlForQuery(ImagerySet imagerySet, string query, Size size)
        {
            return CreateImageryUrlForQuery(imagerySet, query, MapLayer.TrafficFlow, size, MapVersion.V1);
        }

        public static ImagerySearchCriterion CreateImageryUrlForQuery(ImagerySet imagerySet, string query, MapLayer? layer, Size? size, MapVersion? version)
        {
            if (string.IsNullOrEmpty(query) || query.Trim() == string.Empty)
                throw new ArgumentException("Query is null or empty.", "query");

            var criterion = new ImagerySearchCriterion
            {
                ImagerySet = imagerySet,
                Query = query,
                MapLayer = layer,
                MapSize = size,
                MapVersion = version,
            };

            return criterion;
        }

        #endregion

        #region CreateImageryUrlForCentrePoint

        public static IImageryUrlForCenterPointCriterion CreateImageryUrlForCenterPoint(ImagerySet imagerySet, GeoCoordinate centerPoint, int zoomLevel)
        {
            return CreateImageryUrlForCenterPoint(imagerySet, centerPoint, zoomLevel, new List<PushPin>(), MapLayer.TrafficFlow, Size.Empty, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointCriterion CreateImageryUrlForCenterPoint(ImagerySet imagerySet, GeoCoordinate centerPoint, int zoomLevel, IList<PushPin> pushPins)
        {
            return CreateImageryUrlForCenterPoint(imagerySet, centerPoint, zoomLevel, pushPins, MapLayer.TrafficFlow, Size.Empty, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointCriterion CreateImageryUrlForCenterPoint(ImagerySet imagerySet, GeoCoordinate centerPoint, int zoomLevel, Size size)
        {
            return CreateImageryUrlForCenterPoint(imagerySet, centerPoint, zoomLevel, new List<PushPin>(), MapLayer.TrafficFlow, size, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointCriterion CreateImageryUrlForCenterPoint(ImagerySet imagerySet, GeoCoordinate centerPoint, int zoomLevel, IList<PushPin> pushPins, Size size)
        {
            return CreateImageryUrlForCenterPoint(imagerySet, centerPoint, zoomLevel, pushPins, MapLayer.TrafficFlow, size, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointCriterion CreateImageryUrlForCenterPoint(ImagerySet imagerySet, GeoCoordinate centerPoint, int zoomLevel, MapLayer? layer, Size? size, MapVersion? version)
        {
           return CreateImageryUrlForCenterPoint(imagerySet, centerPoint, zoomLevel, new List<PushPin>(), MapLayer.TrafficFlow, size, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointCriterion CreateImageryUrlForCenterPoint(ImagerySet imagerySet, GeoCoordinate centerPoint, int zoomLevel, IList<PushPin> pushPins, MapLayer? layer, Size? size, MapVersion? version)
        {
            if (centerPoint == null || centerPoint == GeoCoordinate.Unknown)
                throw new ArgumentException("CenterPoint is null or Unknown.", "centerPoint");

            if (zoomLevel < 1 || zoomLevel > 22)
                throw new ArgumentException("ZoomLevel is invalid, value must be between 1 and 22.", "zoomLevel");

            if (pushPins != null && pushPins.Count > 18)
                throw new ArgumentOutOfRangeException("pushPins", "Maximum number of PushPins is 18.");

            var criterion = new ImagerySearchCriterion
            {
                ImagerySet = imagerySet,
                CenterPoint = centerPoint,
                ZoomLevel = zoomLevel,
                MapLayer = layer,
                MapSize = size,
                MapVersion = version
            };

            criterion.PushPins.AddRange(pushPins);

            return criterion;
        }
        
        #endregion

        #region CreateImageryUrlForCentrePointWithRoute

        public static IImageryUrlForCenterPointWithRouteCriterion CreateImageryUrlForCentrePointWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, GeoCoordinate centerPoint, int zoomLevel, IList<WayPoint> wayPoints)
        {
            return CreateImageryUrlForCentrePointWithRoute(imagerySet, modeOfTravel, centerPoint, zoomLevel, wayPoints, Size.Empty, new Avoid(), new List<PushPin>(), TimeType.Departure, null, MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointWithRouteCriterion CreateImageryUrlForCentrePointWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, GeoCoordinate centerPoint, int zoomLevel, IList<WayPoint> wayPoints, Size size)
        {
            return CreateImageryUrlForCentrePointWithRoute(imagerySet, modeOfTravel, centerPoint, zoomLevel, wayPoints, size, new Avoid(), new List<PushPin>(), TimeType.Departure, null, MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointWithRouteCriterion CreateImageryUrlForCentrePointWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, GeoCoordinate centerPoint, int zoomLevel, IList<WayPoint> wayPoints, Size size, Avoid avoid)
        {
            return CreateImageryUrlForCentrePointWithRoute(imagerySet, modeOfTravel, centerPoint, zoomLevel, wayPoints, size, avoid, new List<PushPin>(), TimeType.Departure, null, MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointWithRouteCriterion CreateImageryUrlForCentrePointWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, GeoCoordinate centerPoint, int zoomLevel, IList<WayPoint> wayPoints, Size size, IList<PushPin> pushPins)
        {
            return CreateImageryUrlForCentrePointWithRoute(imagerySet, modeOfTravel, centerPoint, zoomLevel, wayPoints, size, new Avoid(), pushPins, TimeType.Departure, null, MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrlForCenterPointWithRouteCriterion CreateImageryUrlForCentrePointWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, GeoCoordinate centerPoint, int zoomLevel, IList<WayPoint> wayPoints, Size? size, Avoid avoid, IList<PushPin> pushPins, TimeType? timeType, DateTime? dateTime, MapLayer? layer, MapVersion? version)
        {
            if (centerPoint == null || centerPoint == GeoCoordinate.Unknown)
                throw new ArgumentException("CenterPoint is null or Unknown.", "centerPoint");

            if (zoomLevel < 1 || zoomLevel > 22)
                throw new ArgumentException("ZoomLevel is invalid, value must be between 1 and 22.", "zoomLevel");

            if (wayPoints == null || wayPoints.Count < 2)
                throw new ArgumentException("Minimum number of WayPoints is 2.", "wayPoints");

            if (pushPins != null && pushPins.Count > 18)
                throw new ArgumentOutOfRangeException("pushPins", "Maximum number of PushPins is 18.");
            
            var criterion = new ImagerySearchCriterion
            {
                Avoid = avoid,
                CenterPoint = centerPoint,
                DateTime = dateTime,
                ImagerySet = imagerySet,
                MapLayer = layer,
                MapSize = size,
                MapVersion = version,
                TimeType = timeType,
                TravelMode = modeOfTravel,
                ZoomLevel = zoomLevel
            };

            criterion.PushPins.AddRange(pushPins);
            criterion.WayPoints.AddRange(wayPoints);

            return criterion;
        }

        #endregion

        #region CreateImageryUrlForSpecificArea

        public static IImageryUrForSpecificAreaCriterion CreateImageryUrlForSpecificArea(ImagerySet imagerySet, LocationRect area)
        {
            return CreateImageryUrlForSpecificArea(imagerySet, area, Size.Empty, new List<PushPin>(), MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrForSpecificAreaCriterion CreateImageryUrlForSpecificArea(ImagerySet imagerySet, LocationRect area, Size size)
        {
            return CreateImageryUrlForSpecificArea(imagerySet, area, size, new List<PushPin>(), MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrForSpecificAreaCriterion CreateImageryUrlForSpecificArea(ImagerySet imagerySet, LocationRect area, Size size, IList<PushPin> pushPins, MapLayer? layer, MapVersion? version)
        {
            if (area == null)
                throw new ArgumentException("MapArea is null or Unknown.", "area");

            if (pushPins != null && pushPins.Count > 18)
                throw new ArgumentOutOfRangeException("pushPins", "Maximum number of PushPins is 18.");

            var criterion = new ImagerySearchCriterion
            {
                ImagerySet = imagerySet,
                MapArea = area,
                MapLayer = layer,
                MapSize = size,
                MapVersion = version,
            };

            criterion.PushPins.AddRange(pushPins);

            return criterion;
        }

        #endregion

        #region CreateImageryUrlForSpecificAreaWithRoute

        public static IImageryUrForSpecificAreaWithRouteCriterion CreateImageryUrlForSpecificAreaWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints, LocationRect area)
        {
            return CreateImageryUrlForSpecificAreaWithRoute(imagerySet, modeOfTravel, wayPoints, area, new Avoid(), new List<PushPin>(), TimeType.Departure, null, 1, MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrForSpecificAreaWithRouteCriterion CreateImageryUrlForSpecificAreaWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints, LocationRect area, Avoid avoid)
        {
            return CreateImageryUrlForSpecificAreaWithRoute(imagerySet, modeOfTravel, wayPoints, area, avoid, new List<PushPin>(), TimeType.Departure, null, 1, MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrForSpecificAreaWithRouteCriterion CreateImageryUrlForSpecificAreaWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints, LocationRect area, Avoid avoid, List<PushPin> pushPins)
        {
            return CreateImageryUrlForSpecificAreaWithRoute(imagerySet, modeOfTravel, wayPoints, area, avoid, pushPins, TimeType.Departure, null, 1, MapLayer.TrafficFlow, MapVersion.V1);
        }

        public static IImageryUrForSpecificAreaWithRouteCriterion CreateImageryUrlForSpecificAreaWithRoute(ImagerySet imagerySet, ModeOfTravel modeOfTravel, IList<WayPoint> wayPoints, LocationRect area, Avoid avoid, IList<PushPin> pushPins, TimeType? timeType, DateTime? dateTime, int? maxSolutions, MapLayer? layer, MapVersion? version)
        {
            if (wayPoints == null || wayPoints.Count < 2)
                throw new ArgumentException("Minimum number of WayPoints is 2.", "wayPoints");

            if (area == null)
                throw new ArgumentException("MapArea is null or Unknown.", "area");

            if (pushPins != null && pushPins.Count > 18)
                throw new ArgumentOutOfRangeException("pushPins", "Maximum number of PushPins is 18.");

            var criterion = new ImagerySearchCriterion
            {
                Avoid = avoid,
                DateTime = dateTime,
                ImagerySet = imagerySet,
                MapArea = area,
                MapLayer = layer,
                MapVersion = version,
                MaxSolutions = maxSolutions,
                TimeType = timeType,
                TravelMode = modeOfTravel
            };

            criterion.PushPins.AddRange(pushPins);
            criterion.WayPoints.AddRange(wayPoints);

            return criterion;
        }

        #endregion

        #region GetImageryMetadataForAnImagerySet

        public static IImageryMetadataForAnImagerySetCriterion CreateImageryMetadataForAnImagerySet(ImagerySet imagerySet)
        {
            if (imagerySet == ImagerySet.Birdseye || imagerySet == ImagerySet.BirdseyeWithLabels)
                throw new ArgumentException("Birdseye or BirdseyeWithLabels imagerySet not allowed!", "imagerySet");
            
            return new ImageryMetadataCriterion { ImagerySet = imagerySet };
        }

        public static IImageryMetadataForAnImagerySetCriterion CreateImageryMetadataForAnImagerySet()
        {
            return CreateImageryMetadataForAnImagerySet(ImagerySet.Road);
        }
        
        #endregion

        #region CreateImageryMetadataForAnImagerySetAtSpecificLocationCriterion

        public static IImageryMetadataForAnImagerySetAtSpecificLocationCriterion CreateImageryMetadataForAnImagerySetAtSpecificLocationCriterion(ImagerySet imagerySet, GeoCoordinate centerPoint)
        {
            return CreateImageryMetadataForAnImagerySetAtSpecificLocationCriterion(imagerySet, centerPoint, null, null, null);
        }

        public static IImageryMetadataForAnImagerySetAtSpecificLocationCriterion CreateImageryMetadataForAnImagerySetAtSpecificLocationCriterion(ImagerySet imagerySet, GeoCoordinate centerPoint, int? zoomLevel)
        {
            return CreateImageryMetadataForAnImagerySetAtSpecificLocationCriterion(imagerySet, centerPoint, null, zoomLevel, null);
        }

        public static IImageryMetadataForAnImagerySetAtSpecificLocationCriterion CreateImageryMetadataForAnImagerySetAtSpecificLocationCriterion(ImagerySet imagerySet, GeoCoordinate centerPoint, int? orientation, int? zoomLevel)
        {
            return CreateImageryMetadataForAnImagerySetAtSpecificLocationCriterion(imagerySet, centerPoint, orientation, zoomLevel, null);
        }

        public static IImageryMetadataForAnImagerySetAtSpecificLocationCriterion CreateImageryMetadataForAnImagerySetAtSpecificLocationCriterion(ImagerySet imagerySet, GeoCoordinate centerPoint, int? orientation, int? zoomLevel, MetadataInclude? include)
        {
            if (orientation != null &&(orientation < 0 || orientation > 360))
                throw new ArgumentException("Orientation is invalid, value must be between 0 and 360.", "orientation");

            if (imagerySet == ImagerySet.Birdseye || imagerySet == ImagerySet.BirdseyeWithLabels)
            {
                if (centerPoint == null || centerPoint == GeoCoordinate.Unknown)
                    throw new ArgumentException("CenterPoint is null or Unknown.", "centerPoint");
            }

            if (imagerySet == ImagerySet.Road || imagerySet == ImagerySet.Aerial || imagerySet == ImagerySet.AerialWithLabels)
            {
                if (zoomLevel < 1 || zoomLevel > 22)
                    throw new ArgumentException("ZoomLevel is invalid, value must be between 1 and 22.", "zoomLevel");
            }

            var criterion = new ImageryMetadataCriterion
            {
                ImagerySet = imagerySet,
                CenterPoint = centerPoint,
                Orientation = orientation,
                ZoomLevel = zoomLevel,
                Include = include
            };

            return criterion;
        }

        #endregion

        #region CreateImageryMetadataForBasicImagerySetAtSpecificLocationCriterion

        public static IImageryMetadataForBasicImagerySetAtSpecificLocationCriterion CreateImageryMetadataForBasicImagerySetAtSpecificLocationCriterion(ImagerySet imagerySet, GeoCoordinate centerPoint)
        {
            return CreateImageryMetadataForBasicImagerySetAtSpecificLocationCriterion(imagerySet, centerPoint, null, null, null);
        }

        public static IImageryMetadataForBasicImagerySetAtSpecificLocationCriterion CreateImageryMetadataForBasicImagerySetAtSpecificLocationCriterion(ImagerySet imagerySet, GeoCoordinate centerPoint, int? zoomLevel)
        {
            return CreateImageryMetadataForBasicImagerySetAtSpecificLocationCriterion(imagerySet, centerPoint, null, zoomLevel, null);
        }

        public static IImageryMetadataForBasicImagerySetAtSpecificLocationCriterion CreateImageryMetadataForBasicImagerySetAtSpecificLocationCriterion(ImagerySet imagerySet, GeoCoordinate centerPoint, int? orientation, int? zoomLevel)
        {
            return CreateImageryMetadataForBasicImagerySetAtSpecificLocationCriterion(imagerySet, centerPoint, orientation, zoomLevel, null);
        }

        public static IImageryMetadataForBasicImagerySetAtSpecificLocationCriterion CreateImageryMetadataForBasicImagerySetAtSpecificLocationCriterion(ImagerySet imagerySet, GeoCoordinate centerPoint, int? orientation, int? zoomLevel, MetadataInclude? include)
        {
            if (orientation != null && (orientation < 0 || orientation > 360))
                throw new ArgumentException("Orientation is invalid, value must be between 0 and 360.", "orientation");

            if (imagerySet == ImagerySet.Birdseye || imagerySet == ImagerySet.BirdseyeWithLabels)
            {
                if (centerPoint == null || centerPoint == GeoCoordinate.Unknown)
                    throw new ArgumentException("CenterPoint is null or Unknown.", "centerPoint");
            }

            if (imagerySet == ImagerySet.Road || imagerySet == ImagerySet.Aerial || imagerySet == ImagerySet.AerialWithLabels)
            {
                if (zoomLevel < 1 || zoomLevel > 22)
                    throw new ArgumentException("ZoomLevel is invalid, value must be between 1 and 22.", "zoomLevel");
            }

            var criterion = new ImageryMetadataCriterion
            {
                ImagerySet = imagerySet,
                CenterPoint = centerPoint,
                Orientation = orientation,
                ZoomLevel = zoomLevel,
                Include = include
            };

            return criterion;
        }

        #endregion
        
        #region SearchForServices

        public static ISearchCriterion CreateSearchForServices(LocationRect boundingRectangle, string query, CultureInfo culture)
        {
            return CreateSearchForServices(boundingRectangle, query, culture, 10, 50, SourceType.Phonebook);
        }

        public static ISearchCriterion CreateSearchForServices(LocationRect boundingRectangle, string query, CultureInfo culture, int radius, int resultSize)
        {
            return CreateSearchForServices(boundingRectangle, query, culture, radius, resultSize, SourceType.Phonebook);
        }

        public static ISearchCriterion CreateSearchForServices(LocationRect boundingRectangle, string query, CultureInfo culture, int radius, int resultSize, SourceType source)
        {
            if (boundingRectangle == null)
                throw new ArgumentNullException("boundingRectangle", "Bounding Rectangle can not be null.");

            if (string.IsNullOrEmpty(query))
                throw new ArgumentNullException("query", "Query is empty or null.");

            if (radius < 0)
                throw new ArgumentException("Radius can not be less than 0.", "radius");

            if (radius > 10)
                throw new ArgumentException("Radius can not be greater than 10.", "radius");

            if (resultSize < 0)
                throw new ArgumentException("Result Size can not be less than 0.", "resultSize");

            if (resultSize > 50)
                throw new ArgumentException("Result Size can not be greater than 50.", "resultSize");

            return new SearchCriterion{BoundingRectangle = boundingRectangle, Query = query, Culture  = culture, Radius = radius, ResultSize = resultSize, Source = source};
        }

        #endregion
    }
}